# Copyright (C) 2008 Luke Kenneth Casson Leighton <lkcl@lkcl.net>
# Copyright (C) 2008 Martin Soto <soto@freedesktop.org>
# Copyright (C) 2008 Alp Toker <alp@atoker.com>
# Copyright (C) 2009 Adam Dingle <adam@yorba.org>
# Copyright (C) 2009 Jim Nelson <jim@yorba.org>
# Copyright (C) 2009, 2010 Igalia S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library; see the file COPYING.LIB.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

package CodeGeneratorGObject;

use constant FileNamePrefix => "WebKitDOM";
use File::Basename;
use FindBin;

# Global Variables
my %implIncludes = ();
my %hdrIncludes = ();

my @stableSymbols = ();

my $defineTypeMacro = "G_DEFINE_TYPE";
my $defineTypeInterfaceImplementation = ")";
my @txtEventListeners = ();
my @txtInstallProps = ();
my @txtSetProps = ();
my @txtGetProps = ();

my $className = "";

# FIXME: this should be replaced with a function that recurses up the tree
# to find the actual base type.
my %baseTypeHash = ("Object" => 1, "Node" => 1, "NodeList" => 1, "NamedNodeMap" => 1, "DOMImplementation" => 1,
                    "Event" => 1, "CSSRule" => 1, "CSSValue" => 1, "StyleSheet" => 1, "MediaList" => 1,
                    "Counter" => 1, "Rect" => 1, "RGBColor" => 1, "XPathExpression" => 1, "XPathResult" => 1,
                    "NodeIterator" => 1, "TreeWalker" => 1, "AbstractView" => 1, "Blob" => 1, "DOMTokenList" => 1,
                    "HTMLCollection" => 1, "TextTrackCue" => 1);

# Only objects derived from Node are released by the DOM object cache and can be
# transfer none. Ideally we could use GetBaseClass with the parent type to check
# whether it's Node, but unfortunately we only have the name of the return type,
# and we can't know its parent base class. Since there are fewer classes in the
# API that are not derived from Node, we will list them here to decide the
# transfer type.
my %transferFullTypeHash = ("AudioTrack" => 1, "AudioTrackList" => 1, "BarProp" => 1, "BatteryManager" => 1,
    "CSSRuleList" => 1, "CSSStyleDeclaration" => 1, "CSSStyleSheet" => 1,
    "DOMApplicationCache" => 1, "DOMMimeType" => 1, "DOMMimeTypeArray" => 1, "DOMNamedFlowCollection" => 1,
    "DOMPlugin" => 1, "DOMPluginArray" => 1, "DOMSecurityPolicy" => 1,
    "DOMSelection" => 1, "DOMSettableTokenList" => 1, "DOMStringList" => 1,
    "DOMWindow" => 1, "DOMWindowCSS" => 1, "EventTarget" => 1,
    "File" => 1, "FileList" => 1, "Gamepad" => 1, "GamepadList" => 1,
    "Geolocation" => 1, "HTMLOptionsCollection" => 1, "History" => 1,
    "KeyboardEvent" => 1, "MediaError" => 1, "MediaController" => 1,
    "MouseEvent" => 1, "MediaQueryList" => 1, "Navigator" => 1, "NodeFilter" => 1,
    "Performance" => 1, "PerformanceEntry" => 1, "PerformanceEntryList" => 1, "PerformanceNavigation" => 1, "PerformanceTiming" => 1,
    "Range" => 1, "Screen" => 1, "SpeechSynthesis" => 1, "SpeechSynthesisVoice" => 1,
    "Storage" => 1, "StyleMedia" => 1, "TextTrack" => 1, "TextTrackCueList" => 1,
    "TimeRanges" => 1, "Touch" => 1, "UIEvent" => 1, "UserMessageHandler" => 1, "UserMessageHandlersNamespace" => 1,
    "ValidityState" => 1, "VideoTrack" => 1, "WebKitNamedFlow" => 1,
    "WebKitNamespace" => 1, "WebKitPoint" => 1, "WheelEvent" => 1, "XPathNSResolver" => 1);

# List of function parameters that are allowed to be NULL
my $canBeNullParams = {
    'webkit_dom_document_create_attribute_ns' => ['namespaceURI'],
    'webkit_dom_document_create_element_ns' => ['namespaceURI'],
    'webkit_dom_document_create_entity_reference' => ['name'],
    'webkit_dom_document_create_node_iterator' => ['filter'],
    'webkit_dom_document_create_tree_walker' => ['filter'],
    'webkit_dom_document_evaluate' => ['inResult', 'resolver'],
    'webkit_dom_document_get_override_style' => ['pseudoElement'],
    'webkit_dom_dom_implementation_create_document' => ['namespaceURI', 'doctype'],
    'webkit_dom_dom_window_get_computed_style' => ['pseudoElement'],
    'webkit_dom_element_set_attribute_ns' => ['namespaceURI'],
    'webkit_dom_node_insert_before' => ['refChild'],
};

# Default constructor
sub new {
    my $object = shift;
    my $reference = { };

    $codeGenerator = shift;

    bless($reference, $object);
}

my $licenceTemplate = << "EOF";
/*
 *  This file is part of the WebKit open source project.
 *  This file has been generated by generate-bindings.pl. DO NOT MODIFY!
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */
EOF

sub GetParentClassName {
    my $interface = shift;

    return "WebKitDOMObject" unless $interface->parent;
    return "WebKitDOM" . $interface->parent;
}

sub GetParentImplClassName {
    my $interface = shift;

    return "Object" unless $interface->parent;
    return $interface->parent;
}

sub IsBaseType
{
    my $type = shift;

    return 1 if $baseTypeHash{$type};
    return 0;
}

sub GetBaseClass
{
    $parent = shift;
    $interface = shift;

    return $parent if $parent eq "Object" or IsBaseType($parent);
    return "Event" if $codeGenerator->InheritsInterface($interface, "Event");
    return "CSSValue" if $parent eq "SVGColor" or $parent eq "CSSValueList";
    return "Node";
}


# From String::CamelCase 0.01
sub camelize
{
        my $s = shift;
        join('', map{ ucfirst $_ } split(/(?<=[A-Za-z])_(?=[A-Za-z])|\b/, $s));
}

sub decamelize
{
        my $s = shift;
        $s =~ s{([^a-zA-Z]?)([A-Z]*)([A-Z])([a-z]?)}{
                my $fc = pos($s)==0;
                my ($p0,$p1,$p2,$p3) = ($1,lc$2,lc$3,$4);
                my $t = $p0 || $fc ? $p0 : '_';
                $t .= $p3 ? $p1 ? "${p1}_$p2$p3" : "$p2$p3" : "$p1$p2";
                $t;
        }ge;

        # Some strings are not correctly decamelized, apply fix ups
        for ($s) {
            s/domcss/dom_css/;
            s/domhtml/dom_html/;
            s/domdom/dom_dom/;
            s/domcdata/dom_cdata/;
            s/domui/dom_ui/;
            s/x_path/xpath/;
            s/web_kit/webkit/;
            s/htmli_frame/html_iframe/;
            s/htmlbr/html_br/;
            s/htmlli/html_li/;
            s/htmlhr/html_hr/;
            s/htmld/html_d/;
            s/htmlo/html_o/;
            s/htmlu/html_u/;
        }
        return $s;
}

sub HumanReadableConditional {
    my @conditional = split('_', shift);
    my @upperCaseExceptions = ("SQL", "API");
    my @humanReadable;

    for $part (@conditional) {
        if (!grep {$_ eq $part} @upperCaseExceptions) {
            $part = camelize(lc($part));
        }
        push(@humanReadable, $part);
    }

    return join(' ', @humanReadable);
}

sub GetParentGObjType {
    my $interface = shift;

    return "WEBKIT_DOM_TYPE_OBJECT" unless $interface->parent;
    return "WEBKIT_DOM_TYPE_" . uc(decamelize(($interface->parent)));
}

sub GetClassName {
    my $name = shift;

    return "WebKitDOM$name";
}

sub SkipAttribute {
    my $attribute = shift;

    if ($attribute->signature->extendedAttributes->{"Custom"}
        || $attribute->signature->extendedAttributes->{"CustomGetter"}) {
        return 1;
    }

    my $propType = $attribute->signature->type;
    if ($propType =~ /Constructor$/) {
        return 1;
    }

    return 1 if $attribute->isStatic;
    return 1 if $codeGenerator->IsTypedArrayType($propType);

    $codeGenerator->AssertNotSequenceType($propType);

    if ($codeGenerator->GetArrayType($propType)) {
        return 1;
    }

    if ($codeGenerator->IsEnumType($propType)) {
        return 1;
    }

    # This is for DOMWindow.idl location attribute
    if ($attribute->signature->name eq "location") {
        return 1;
    }

    # This is for HTMLInput.idl valueAsDate
    if ($attribute->signature->name eq "valueAsDate") {
        return 1;
    }

    # This is for DOMWindow.idl Crypto attribute
    if ($attribute->signature->type eq "Crypto") {
        return 1;
    }

    if ($attribute->signature->type eq "EventListener") {
        return 1;
    }

    if ($attribute->signature->type eq "MediaQueryListListener") {
        return 1;
    }

    # Skip indexed database attributes for now, they aren't yet supported for the GObject generator.
    if ($attribute->signature->name =~ /^(?:webkit)?[Ii]ndexedDB/ or $attribute->signature->name =~ /^(?:webkit)?IDB/) {
        return 1;
    }

    return 0;
}

sub SkipFunction {
    my $object = shift;
    my $function = shift;
    my $parentNode = shift;
    my $decamelize = shift;
    my $prefix = shift;

    my $functionName = "webkit_dom_" . $decamelize . "_" . $prefix . decamelize($function->signature->name);
    my $functionReturnType = $prefix eq "set_" ? "void" : $function->signature->type;
    my $isCustomFunction = $function->signature->extendedAttributes->{"Custom"};
    my $callWith = $function->signature->extendedAttributes->{"CallWith"};
    my $isUnsupportedCallWith = $codeGenerator->ExtendedAttributeContains($callWith, "ScriptArguments") || $codeGenerator->ExtendedAttributeContains($callWith, "CallStack");

    if (($isCustomFunction || $isUnsupportedCallWith) &&
        $functionName ne "webkit_dom_node_replace_child" &&
        $functionName ne "webkit_dom_node_insert_before" &&
        $functionName ne "webkit_dom_node_remove_child" &&
        $functionName ne "webkit_dom_node_append_child" &&
        $functionName ne "webkit_dom_html_collection_item" &&
        $functionName ne "webkit_dom_html_collection_named_item") {
        return 1;
    }

    # Skip functions that have callback parameters, because this code generator doesn't know
    # how to auto-generate callbacks.  Skip functions that have "MediaQueryListListener" or
    # sequence<T> parameters, because this code generator doesn't know how to auto-generate
    # MediaQueryListListener or sequence<T>. Skip EventListeners because they are handled elsewhere.
    foreach my $param (@{$function->parameters}) {
        if ($codeGenerator->IsCallbackInterface($param->type) ||
            $param->extendedAttributes->{"Clamp"} ||
            $param->type eq "MediaQueryListListener" ||
            $param->type eq "EventListener" ||
            $codeGenerator->GetSequenceType($param->type)) {
            return 1;
        }
    }

    # This is for DataTransferItemList.idl add(File) method
    if ($functionName eq "webkit_dom_data_transfer_item_list_add" && @{$function->parameters} == 1) {
        return 1;
    }

    # Skip dispatch_event methods.
    if ($parentNode->extendedAttributes->{"EventTarget"} && $function->signature->name eq "dispatchEvent") {
        return 1;
    }

    # Skip Console::profile() and Console::profileEnd() as they're not correctly generated for the moment.
    if ($functionName eq "webkit_dom_console_profile" || $functionName eq "webkit_dom_console_profile_end") {
        return 1;
    }

    if ($codeGenerator->IsTypedArrayType($function->signature->type) || $codeGenerator->GetArrayType($function->signature->type)) {
        return 1;
    }

    if ($function->signature->name eq "set" and $parentNode->extendedAttributes->{"TypedArray"}) {
        return 1;
    }

    if ($object eq "MediaQueryListListener") {
        return 1;
    }

    if ($function->signature->name eq "getSVGDocument") {
        return 1;
    }

    if ($function->signature->name eq "getCSSCanvasContext") {
        return 1;
    }

    if ($function->signature->name eq "setRangeText" && @{$function->parameters} == 1) {
        return 1;
    }

    if ($function->signature->name eq "timeEnd") {
        return 1;
    }

    if ($codeGenerator->GetSequenceType($functionReturnType)) {
        return 1;
    }

    if ($function->signature->name eq "supports" && @{$function->parameters} == 1) {
        return 1;
    }

    return 0;
}

# Name type used in the g_value_{set,get}_* functions
sub GetGValueTypeName {
    my $type = shift;

    my %types = ("DOMString", "string",
                 "DOMTimeStamp", "uint",
                 "float", "float",
                 "unrestricted float", "float",
                 "double", "double",
                 "unrestricted double", "double",
                 "boolean", "boolean",
                 "char", "char",
                 "long", "long",
                 "long long", "int64",
                 "byte", "int8",
                 "octet", "uint8",
                 "short", "int",
                 "uchar", "uchar",
                 "unsigned", "uint",
                 "int", "int",
                 "unsigned int", "uint",
                 "unsigned long long", "uint64", 
                 "unsigned long", "ulong",
                 "unsigned short", "uint");

    return $types{$type} ? $types{$type} : "object";
}

# Name type used in C declarations
sub GetGlibTypeName {
    my $type = shift;
    my $name = GetClassName($type);

    my %types = ("DOMString", "gchar*",
                 "DOMTimeStamp", "guint32",
                 "CompareHow", "gushort",
                 "float", "gfloat",
                 "unrestricted float", "gfloat",
                 "double", "gdouble",
                 "unrestricted double", "gdouble",
                 "boolean", "gboolean",
                 "char", "gchar",
                 "long", "glong",
                 "long long", "gint64",
                 "byte", "gint8",
                 "octet", "guint8",
                 "short", "gshort",
                 "uchar", "guchar",
                 "unsigned", "guint",
                 "int", "gint",
                 "unsigned int", "guint",
                 "unsigned long", "gulong",
                 "unsigned long long", "guint64",
                 "unsigned short", "gushort",
                 "void", "void");

    return $types{$type} ? $types{$type} : "$name*";
}

sub IsGDOMClassType {
    my $type = shift;

    return 0 if $codeGenerator->IsNonPointerType($type) || $codeGenerator->IsStringType($type);
    return 1;
}

sub IsPropertyReadable {
    my $property = shift;
    return !SkipAttribute($property);
}

sub IsPropertyWriteable {
    my $property = shift;

    if (!IsPropertyReadable($property)) {
        return 0;
    }

    if ($property->isReadOnly) {
        return 0;
    }

    my $gtype = GetGValueTypeName($property->signature->type);
    my $hasGtypeSignature = $gtype eq "boolean" || $gtype eq "float" || $gtype eq "double" ||
                            $gtype eq "int64" || $gtype eq "uint64" ||
                            $gtype eq "long" || $gtype eq "ulong" ||
                            $gtype eq "int" || $gtype eq "uint" ||
                            $gtype eq "short" || $gtype eq "ushort" ||
                            $gtype eq "int8" || $gtype eq "uint8" ||
                            $gtype eq "char" || $gtype eq "uchar" ||
                            $gtype eq "string";
    if (!$hasGtypeSignature) {
        return 0;
    }

    # FIXME: We are not generating setters for 'Replaceable' attributes now, but we should somehow.
    if ($property->signature->extendedAttributes->{"Replaceable"}) {
        return 0;
    }

    if ($property->signature->extendedAttributes->{"CustomSetter"}) {
        return 0;
    }

    return 1;
}

sub GenerateConditionalWarning
{
    my $node = shift;
    my $indentSize = shift;
    if (!$indentSize) {
        $indentSize = 4;
    }

    my $conditional = $node->extendedAttributes->{"Conditional"};
    my @warn;

    if ($conditional) {
        if ($conditional =~ /&/) {
            my @splitConditionals = split(/&/, $conditional);
            foreach $condition (@splitConditionals) {
                push(@warn, "#if !ENABLE($condition)\n");
                push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n");
                push(@warn, "#endif\n");
            }
        } elsif ($conditional =~ /\|/) {
            foreach $condition (split(/\|/, $conditional)) {
                push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($condition) . "\")\n");
            }
        } else {
            push(@warn, ' ' x $indentSize . "WEBKIT_WARN_FEATURE_NOT_PRESENT(\"" . HumanReadableConditional($conditional) . "\")\n");
        }
    }

    return @warn;
}

sub GenerateProperty {
    my $attribute = shift;
    my $interfaceName = shift;
    my @writeableProperties = @{shift @_};
    my $parentNode = shift;

    my $hasGetterException = $attribute->signature->extendedAttributes->{"GetterRaisesException"};
    my $hasSetterException = $attribute->signature->extendedAttributes->{"SetterRaisesException"};

    my $decamelizeInterfaceName = decamelize($interfaceName);
    my $propName = decamelize($attribute->signature->name);
    my $propFunctionName = GetFunctionSignatureName($interfaceName, $attribute);
    my $propNameCaps = uc($propName);
    my ${propEnum} = "PROP_${propNameCaps}";
    push(@cBodyProperties, "    ${propEnum},\n");

    my $propType = $attribute->signature->type;
    my ${propGType} = decamelize($propType);
    my ${ucPropGType} = uc($propGType);

    my $gtype = GetGValueTypeName($propType);
    my $gparamflag = "WEBKIT_PARAM_READABLE";
    my $writeable = IsPropertyWriteable($attribute);

    my $mutableString = "read-only";
    my $hasCustomSetter = $attribute->signature->extendedAttributes->{"CustomSetter"};
    if ($writeable && $hasCustomSetter) {
        $mutableString = "read-only (due to custom functions needed in webkitdom)";
    } elsif ($writeable) {
        $gparamflag = "WEBKIT_PARAM_READWRITE";
        $mutableString = "read-write";
    }

    my @getterArguments = ();
    push(@getterArguments, "self");
    push(@getterArguments, "nullptr") if $hasGetterException;

    my @setterArguments = ();
    push(@setterArguments, "self, g_value_get_$gtype(value)");
    push(@setterArguments, "nullptr") if $hasSetterException;

    if (grep {$_ eq $attribute} @writeableProperties) {
        push(@txtSetProps, "    case ${propEnum}:\n");
        push(@txtSetProps, "        webkit_dom_${decamelizeInterfaceName}_set_" . $propFunctionName . "(" . join(", ", @setterArguments) . ");\n");
        push(@txtSetProps, "        break;\n");
    }

    push(@txtGetProps, "    case ${propEnum}:\n");

    # FIXME: Should we return a default value when isNull == true?

    my $postConvertFunction = "";
    if ($gtype eq "string") {
        push(@txtGetProps, "        g_value_take_string(value, webkit_dom_${decamelizeInterfaceName}_get_" . $propFunctionName . "(" . join(", ", @getterArguments) . "));\n");
    } else {
        push(@txtGetProps, "        g_value_set_$gtype(value, webkit_dom_${decamelizeInterfaceName}_get_" . $propFunctionName . "(" . join(", ", @getterArguments) . "));\n");
    }

    push(@txtGetProps, "        break;\n");

    my %parameterSpecOptions = ("int" =>     [ "G_MININT", "G_MAXINT", "0" ],
                                "int8" =>    [ "G_MININT8", "G_MAXINT8", "0" ],
                                "boolean" => [ "FALSE" ],
                                "float" =>   [ "-G_MAXFLOAT", "G_MAXFLOAT", "0" ],
                                "double" =>  [ "-G_MAXDOUBLE", "G_MAXDOUBLE", "0" ],
                                "uint64" =>  [ "0", "G_MAXUINT64", "0" ],
                                "long" =>    [ "G_MINLONG", "G_MAXLONG", "0" ],
                                "int64" =>   [ "G_MININT64", "G_MAXINT64", "0" ],
                                "ulong" =>   [ "0", "G_MAXULONG", "0" ],
                                "uint" =>    [ "0", "G_MAXUINT", "0" ],
                                "uint8" =>   [ "0", "G_MAXUINT8", "0" ],
                                "ushort" =>  [ "0", "G_MAXUINT16", "0" ],
                                "uchar" =>   [ "G_MININT8", "G_MAXINT8", "0" ],
                                "char" =>    [ "0", "G_MAXUINT8", "0" ],
                                "string" =>  [ '""', ],
                                "object" =>  [ "WEBKIT_DOM_TYPE_${ucPropGType}" ]);

    my $extraParameters = join(", ", @{$parameterSpecOptions{$gtype}});
    my $glibTypeName = GetGlibTypeName($propType);
    $propName =~ s/_/-/g;
    my $txtInstallProp = << "EOF";
    g_object_class_install_property(
        gobjectClass,
        $propEnum,
        g_param_spec_$gtype(
            "$propName",
            "$interfaceName:$propName",
            "$mutableString $glibTypeName $interfaceName:$propName",
            $extraParameters,
            $gparamflag));

EOF
    push(@txtInstallProps, $txtInstallProp);
}

sub GenerateProperties {
    my ($object, $interfaceName, $interface) = @_;

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";
    my $parentImplClassName = GetParentImplClassName($interface);

    my $conditionGuardStart = "";
    my $conditionGuardEnd = "";
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
    if ($conditionalString) {
        $conditionGuardStart = "#if ${conditionalString}";
        $conditionGuardEnd = "#endif // ${conditionalString}";
    }

    # Properties
    my $implContent = "";
    my @readableProperties = grep { IsPropertyReadable($_) } @{$interface->attributes};
    my @writeableProperties = grep { IsPropertyWriteable($_) } @{$interface->attributes};
    my $numProperties = scalar @readableProperties;

    # Properties
    if ($numProperties > 0) {
        $implContent = << "EOF";
enum {
    PROP_0,
EOF
        push(@cBodyProperties, $implContent);

        push(@txtGetProps, "static void ${lowerCaseIfaceName}_get_property(GObject* object, guint propertyId, GValue* value, GParamSpec* pspec)\n");
        push(@txtGetProps, "{\n");
        push(@txtGetProps, "    ${className}* self = WEBKIT_DOM_${clsCaps}(object);\n");
        push(@txtGetProps, "\n");
        push(@txtGetProps, "    switch (propertyId) {\n");

        if (scalar @writeableProperties > 0) {
            push(@txtSetProps, "static void ${lowerCaseIfaceName}_set_property(GObject* object, guint propertyId, const GValue* value, GParamSpec* pspec)\n");
            push(@txtSetProps, "{\n");
            push(@txtSetProps, "    ${className}* self = WEBKIT_DOM_${clsCaps}(object);\n");
            push(@txtSetProps, "\n");
            push(@txtSetProps, "    switch (propertyId) {\n");
        }

        foreach my $attribute (@readableProperties) {
            GenerateProperty($attribute, $interfaceName, \@writeableProperties, $interface);
        }

        push(@cBodyProperties, "};\n\n");

        $txtGetProp = << "EOF";
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
        break;
    }
}
EOF
        push(@txtGetProps, $txtGetProp);

        if (scalar @writeableProperties > 0) {
            $txtSetProps = << "EOF";
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propertyId, pspec);
        break;
    }
}
EOF
            push(@txtSetProps, $txtSetProps);
        }
    }

    # Do not insert extra spaces when interpolating array variables
    $" = "";

    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
static void ${lowerCaseIfaceName}_finalize(GObject* object)
{
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object);
$conditionGuardStart
    WebKit::DOMObjectCache::forget(priv->coreObject.get());
$conditionGuardEnd
    priv->~${className}Private();
    G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->finalize(object);
}

EOF
        push(@cBodyProperties, $implContent);
    }

    if ($numProperties > 0) {
        if (scalar @writeableProperties > 0) {
            push(@cBodyProperties, @txtSetProps);
            push(@cBodyProperties, "\n");
        }
        push(@cBodyProperties, @txtGetProps);
        push(@cBodyProperties, "\n");
    }

    # Add a constructor implementation only for direct subclasses of Object to make sure
    # that the WebCore wrapped object is added only once to the DOM cache. The DOM garbage
    # collector works because Node is a direct subclass of Object and the version of
    # DOMObjectCache::put() that receives a Node (which is the one setting the frame) is
    # always called for DOM objects derived from Node.
    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
static GObject* ${lowerCaseIfaceName}_constructor(GType type, guint constructPropertiesCount, GObjectConstructParam* constructProperties)
{
    GObject* object = G_OBJECT_CLASS(${lowerCaseIfaceName}_parent_class)->constructor(type, constructPropertiesCount, constructProperties);
$conditionGuardStart
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(object);
    priv->coreObject = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(object)->coreObject);
    WebKit::DOMObjectCache::put(priv->coreObject.get(), object);
$conditionGuardEnd
    return object;
}

EOF
        push(@cBodyProperties, $implContent);
    }

    $implContent = << "EOF";
static void ${lowerCaseIfaceName}_class_init(${className}Class* requestClass)
{
EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object" || $numProperties > 0) {
        push(@cBodyProperties, "    GObjectClass* gobjectClass = G_OBJECT_CLASS(requestClass);\n");

        if ($parentImplClassName eq "Object") {
            push(@cBodyProperties, "    g_type_class_add_private(gobjectClass, sizeof(${className}Private));\n");
            push(@cBodyProperties, "    gobjectClass->constructor = ${lowerCaseIfaceName}_constructor;\n");
            push(@cBodyProperties, "    gobjectClass->finalize = ${lowerCaseIfaceName}_finalize;\n");
        }

        if ($numProperties > 0) {
            if (scalar @writeableProperties > 0) {
                push(@cBodyProperties, "    gobjectClass->set_property = ${lowerCaseIfaceName}_set_property;\n");
            }
            push(@cBodyProperties, "    gobjectClass->get_property = ${lowerCaseIfaceName}_get_property;\n");
            push(@cBodyProperties, "\n");
            push(@cBodyProperties, @txtInstallProps);
        }
    } else {
        push(@cBodyProperties, "    UNUSED_PARAM(requestClass);\n");
    }
    $implContent = << "EOF";
}

static void ${lowerCaseIfaceName}_init(${className}* request)
{
EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object") {
        $implContent = << "EOF";
    ${className}Private* priv = WEBKIT_DOM_${clsCaps}_GET_PRIVATE(request);
    new (priv) ${className}Private();
EOF
        push(@cBodyProperties, $implContent);
    } else {
        push(@cBodyProperties, "    UNUSED_PARAM(request);\n");
    }
    $implContent = << "EOF";
}

EOF
    push(@cBodyProperties, $implContent);
}

sub GenerateConstants {
    my ($interface, $prefix) = @_;

    my $isStableClass = scalar(@stableSymbols);

    if (@{$interface->constants}) {
        my @constants = @{$interface->constants};
        foreach my $constant (@constants) {
            my $conditionalString = $codeGenerator->GenerateConditionalString($constant);
            my $constantName = $prefix . $constant->name;
            my $constantValue = $constant->value;
            my $isStableSymbol = grep {$_ eq $constantName} @stableSymbols;
            if ($isStableSymbol) {
                push(@symbols, "$constantName\n");
            }

            my @constantHeader = ();
            push(@constantHeader, "#if ${conditionalString}") if $conditionalString;
            push(@constantHeader, "/**");
            push(@constantHeader, " * ${constantName}:");
            push(@constantHeader, " */");
            push(@constantHeader, "#define $constantName $constantValue");
            push(@constantHeader, "#endif /* ${conditionalString} */") if $conditionalString;
            push(@constantHeader, "\n");

            if ($isStableSymbol or !$isStableClass) {
                push(@hBody, join("\n", @constantHeader));
            } else {
                push(@hBodyUnstable, join("\n", @constantHeader));
            }
        }
    }
}

sub GenerateHeader {
    my ($object, $interfaceName, $parentClassName, $interface) = @_;

    my $implContent = "";

    # Add the default header template
    @hPrefix = split("\r", $licenceTemplate);
    push(@hPrefix, "\n");

    my $isStableClass = scalar(@stableSymbols);

    if ($isStableClass) {
        # Force single header include.
        my $headerCheck = << "EOF";
#if !defined(__WEBKITDOM_H_INSIDE__) && !defined(BUILDING_WEBKIT)
#error "Only <webkitdom/webkitdom.h> can be included directly."
#endif

EOF
        push(@hPrefix, $headerCheck);
    }

    # Header guard
    my $guard = $className . "_h";

    @hPrefixGuard = << "EOF";
#ifndef $guard
#define $guard

EOF
    if (!$isStableClass) {
        push(@hPrefixGuard, "#ifdef WEBKIT_DOM_USE_UNSTABLE_API\n\n");
    }

    $implContent = << "EOF";
G_BEGIN_DECLS

EOF

    push(@hBodyPre, $implContent);

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";

    $implContent = << "EOF";
#define WEBKIT_DOM_TYPE_${clsCaps}            (${lowerCaseIfaceName}_get_type())
#define WEBKIT_DOM_${clsCaps}(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), WEBKIT_DOM_TYPE_${clsCaps}, ${className}))
#define WEBKIT_DOM_${clsCaps}_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  WEBKIT_DOM_TYPE_${clsCaps}, ${className}Class)
#define WEBKIT_DOM_IS_${clsCaps}(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), WEBKIT_DOM_TYPE_${clsCaps}))
#define WEBKIT_DOM_IS_${clsCaps}_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  WEBKIT_DOM_TYPE_${clsCaps}))
#define WEBKIT_DOM_${clsCaps}_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  WEBKIT_DOM_TYPE_${clsCaps}, ${className}Class))

EOF

    push(@hBody, $implContent);

    if ($isStableClass) {
        push(@symbols, "GType ${lowerCaseIfaceName}_get_type(void)\n");
    }

    GenerateConstants($interface, "WEBKIT_DOM_${clsCaps}_");

    $implContent = << "EOF";
struct _${className} {
    ${parentClassName} parent_instance;
};

struct _${className}Class {
    ${parentClassName}Class parent_class;
};

EOF

    push(@hBody, $implContent);

    push(@hBody, "WEBKIT_API GType\n${lowerCaseIfaceName}_get_type(void);\n");
    push(@hBody, "\n");
}

sub GetGReturnMacro {
    my ($paramName, $paramIDLType, $returnType, $functionName) = @_;

    my $condition;
    if ($paramIDLType eq "GError") {
        $condition = "!$paramName || !*$paramName";
    } elsif (IsGDOMClassType($paramIDLType)) {
        my $paramTypeCaps = uc(decamelize($paramIDLType));
        $condition = "WEBKIT_DOM_IS_${paramTypeCaps}($paramName)";
        if (ParamCanBeNull($functionName, $paramName)) {
            $condition = "!$paramName || $condition";
        }
    } else {
        if (ParamCanBeNull($functionName, $paramName)) {
            return "";
        }
        $condition = "$paramName";
    }

    my $macro;
    if ($returnType ne "void") {
        $defaultReturn = $returnType eq "gboolean" ? "FALSE" : 0;
        $macro = "    g_return_val_if_fail($condition, $defaultReturn);\n";
    } else {
        $macro = "    g_return_if_fail($condition);\n";
    }

    return $macro;
}

sub ParamCanBeNull {
    my($functionName, $paramName) = @_;

    if (defined($functionName)) {
        return scalar(grep {$_ eq $paramName} @{$canBeNullParams->{$functionName}});
    }
    return 0;
}

sub GetFunctionSignatureName {
    my ($interfaceName, $function) = @_;

    my $signatureName = decamelize($function->signature->name);

    return $signatureName if $signatureName ne "type";

    # For HTML type attribute use type_attr.
    # Example: webkit_dom_html_link_element_get_type_attr()
    my $contentAttributeName = $codeGenerator->ContentAttributeName(\%implIncludes, $interfaceName, $function);
    if ($contentAttributeName) {
        return "type_attr" if $contentAttributeName eq "WebCore::HTMLNames::typeAttr";
    }

    # For methods returning a MIME type use content_type.
    # Examples: webkit_dom_style_sheet_get_content_type(), webkit_dom_dom_mime_type_get_content_type()
    if ($interfaceName eq "StyleSheet" || $interfaceName eq "DOMMimeType") {
        return "content_type";
    }

    # For HTMLFieldSet use field_set_type.
    # Example: webkit_dom_html_field_set_element_get_field_set_type()
    if ($interfaceName eq "HTMLFieldSet") {
        return "field_set_type";
    }

    # For any other cases use the last word of the interface name.
    # Examples: webkit_dom_blob_get_blob_type(), webkit_dom_event_get_event_type()
    my @nameTokens = split('_', decamelize($interfaceName));
    my $name = $nameTokens[-1];

    # If the last word is element and there are more words, use the previous one.
    # Example: webkit_dom_html_button_element_get_button_type()
    if (scalar(@nameTokens) > 1 && $name eq "element") {
        $name = $nameTokens[-2];
    }

    return "${name}_type";
}

sub GetTransferTypeForReturnType {
    my $returnType = shift;

    # Node is always transfer none.
    return "none" if $returnType eq "Node";

    # Any base class but Node is transfer full.
    return "full" if IsBaseType($returnType);

    # Any other class not derived from Node is transfer full.
    return "full" if $transferFullTypeHash{$returnType};
    return "none";
}

sub GenerateFunction {
    my ($object, $interfaceName, $function, $prefix, $parentNode) = @_;

    my $decamelize = decamelize($interfaceName);

    if (SkipFunction($object, $function, $parentNode, $decamelize, $prefix)) {
        return;
    }

    my $functionSigType = $prefix eq "set_" ? "void" : $function->signature->type;
    my $functionSigName = GetFunctionSignatureName($interfaceName, $function);
    my $functionName = "webkit_dom_" . $decamelize . "_" . $prefix . $functionSigName;
    my $returnType = GetGlibTypeName($functionSigType);
    my $returnValueIsGDOMType = IsGDOMClassType($functionSigType);
    my $raisesException = $function->signature->extendedAttributes->{"RaisesException"};

    my $conditionalString = $codeGenerator->GenerateConditionalString($function->signature);
    my $parentConditionalString = $codeGenerator->GenerateConditionalString($parentNode);
    my @conditionalWarn = GenerateConditionalWarning($function->signature);
    my @parentConditionalWarn = GenerateConditionalWarning($parentNode);

    my $functionSig = "${className}* self";
    my $symbolSig = "${className}*";

    my @callImplParams;
    foreach my $param (@{$function->parameters}) {
        my $paramIDLType = $param->type;
        my $arrayOrSequenceType = $codeGenerator->GetArrayOrSequenceType($paramIDLType);
        $paramIDLType = $arrayOrSequenceType if $arrayOrSequenceType ne "";
        my $paramType = GetGlibTypeName($paramIDLType);
        my $const = $paramType eq "gchar*" ? "const " : "";
        my $paramName = $param->name;

        $functionSig .= ", ${const}$paramType $paramName";
        $symbolSig .= ", ${const}$paramType";

        my $paramIsGDOMType = IsGDOMClassType($paramIDLType);
        if ($paramIsGDOMType) {
            if ($paramIDLType ne "any") {
                $implIncludes{"WebKitDOM${paramIDLType}Private.h"} = 1;
            }
        }
        if ($paramIsGDOMType || ($paramIDLType eq "DOMString") || ($paramIDLType eq "CompareHow")) {
            $paramName = "converted" . $codeGenerator->WK_ucfirst($paramName);
        }
        if ($paramIDLType eq "NodeFilter" || $paramIDLType eq "XPathNSResolver") {
            $paramName = "WTF::getPtr(" . $paramName . ")";
        }
        push(@callImplParams, $paramName);
    }

    if ($returnType ne "void" && $returnValueIsGDOMType && $functionSigType ne "any") {
        $implIncludes{"WebKitDOM${functionSigType}Private.h"} = 1;
    }

    $functionSig .= ", GError** error" if $raisesException;
    $symbolSig .= ", GError**" if $raisesException;

    my $symbol = "$returnType $functionName($symbolSig)";
    my $isStableClass = scalar(@stableSymbols);
    my $isStableSymbol = grep {$_ eq $symbol} @stableSymbols;
    if ($isStableSymbol and $isStableClass) {
        push(@symbols, "$symbol\n");
    }

    my @functionHeader = ();
    # Insert introspection annotations
    push(@functionHeader, "/**");
    push(@functionHeader, " * ${functionName}:");
    push(@functionHeader, " * \@self: A #${className}");

    foreach my $param (@{$function->parameters}) {
        my $paramIDLType = $param->type;
        my $arrayOrSequenceType = $codeGenerator->GetArrayOrSequenceType($paramIDLType);
        $paramIDLType = $arrayOrSequenceType if $arrayOrSequenceType ne "";
        my $paramType = GetGlibTypeName($paramIDLType);
        # $paramType can have a trailing * in some cases
        $paramType =~ s/\*$//;
        my $paramName = $param->name;
        my $paramAnnotations = "";
        if (ParamCanBeNull($functionName, $paramName)) {
            $paramAnnotations = " (allow-none):";
        }
        push(@functionHeader, " * \@${paramName}:${paramAnnotations} A #${paramType}");
    }
    push(@functionHeader, " * \@error: #GError") if $raisesException;
    push(@functionHeader, " *");
    my $returnTypeName = $returnType;
    my $hasReturnTag = 0;
    $returnTypeName =~ s/\*$//;
    if ($returnValueIsGDOMType) {
        my $transferType = GetTransferTypeForReturnType($functionSigType);
        push(@functionHeader, " * Returns: (transfer $transferType): A #${returnTypeName}");
        $hasReturnTag = 1;
    } elsif ($returnType ne "void") {
        push(@functionHeader, " * Returns: A #${returnTypeName}");
        $hasReturnTag = 1;
    }
    if (!$isStableSymbol) {
        if ($hasReturnTag) {
            push(@functionHeader, " *");
        }
        push(@functionHeader, " * Stability: Unstable");
    }
    push(@functionHeader, "**/");

    push(@functionHeader, "WEBKIT_API $returnType\n$functionName($functionSig);");
    push(@functionHeader, "\n");
    if ($isStableSymbol or !$isStableClass) {
        push(@hBody, join("\n", @functionHeader));
    } else {
        push(@hBodyUnstable, join("\n", @functionHeader));
    }

    push(@cBody, "$returnType $functionName($functionSig)\n{\n");
    push(@cBody, "#if ${parentConditionalString}\n") if $parentConditionalString;
    push(@cBody, "#if ${conditionalString}\n") if $conditionalString;

    push(@cBody, "    WebCore::JSMainThreadNullState state;\n");

    # g_return macros to check parameters of public methods.
    $gReturnMacro = GetGReturnMacro("self", $interfaceName, $returnType);
    push(@cBody, $gReturnMacro);

    foreach my $param (@{$function->parameters}) {
        my $paramName = $param->name;
        my $paramIDLType = $param->type;
        my $paramTypeIsPointer = !$codeGenerator->IsNonPointerType($paramIDLType);
        if ($paramTypeIsPointer) {
            $gReturnMacro = GetGReturnMacro($paramName, $paramIDLType, $returnType, $functionName);
            push(@cBody, $gReturnMacro);
        }
    }

    if ($raisesException) {
        $gReturnMacro = GetGReturnMacro("error", "GError", $returnType);
        push(@cBody, $gReturnMacro);
    }

    # The WebKit::core implementations check for null already; no need to duplicate effort.
    push(@cBody, "    WebCore::${interfaceName}* item = WebKit::core(self);\n");

    $returnParamName = "";
    foreach my $param (@{$function->parameters}) {
        my $paramIDLType = $param->type;
        my $paramName = $param->name;

        my $paramIsGDOMType = IsGDOMClassType($paramIDLType);
        $convertedParamName = "converted" . $codeGenerator->WK_ucfirst($paramName);
        if ($paramIDLType eq "DOMString") {
            push(@cBody, "    WTF::String ${convertedParamName} = WTF::String::fromUTF8($paramName);\n");
        } elsif ($paramIDLType eq "CompareHow") {
            push(@cBody, "    WebCore::Range::CompareHow ${convertedParamName} = static_cast<WebCore::Range::CompareHow>($paramName);\n");
        } elsif ($paramIDLType eq "NodeFilter" || $paramIDLType eq "XPathNSResolver") {
            push(@cBody, "    RefPtr<WebCore::$paramIDLType> ${convertedParamName} = WebKit::core($paramName);\n");
        } elsif ($paramIsGDOMType) {
            push(@cBody, "    WebCore::${paramIDLType}* ${convertedParamName} = WebKit::core($paramName);\n");
        }
        $returnParamName = $convertedParamName if $param->extendedAttributes->{"CustomReturn"};
    }

    my $assign = "";
    my $assignPre = "";
    my $assignPost = "";

    # We need to special-case these Node methods because their C++
    # signature is different from what we'd expect given their IDL
    # description; see Node.h.
    my $functionHasCustomReturn = $functionName eq "webkit_dom_node_append_child" ||
        $functionName eq "webkit_dom_node_insert_before" ||
        $functionName eq "webkit_dom_node_replace_child" ||
        $functionName eq "webkit_dom_node_remove_child";
         
    if ($returnType ne "void" && !$functionHasCustomReturn) {
        if ($returnValueIsGDOMType) {
            $assign = "RefPtr<WebCore::${functionSigType}> gobjectResult = ";
            $assignPre = "WTF::getPtr(";
            $assignPost = ")";
        } else {
            $assign = "${returnType} result = ";
        }
    }

    # FIXME: Should we return a default value when isNull == true?
    if ($function->signature->isNullable) {
        push(@cBody, "    bool isNull = false;\n");
        push(@callImplParams, "isNull");
    }

    if ($raisesException) {
        push(@cBody, "    WebCore::ExceptionCode ec = 0;\n");
        push(@callImplParams, "ec");
    }

    my $functionImplementationName = $function->signature->extendedAttributes->{"ImplementedAs"} || $function->signature->name;

    if ($functionHasCustomReturn) {
        push(@cBody, "    bool ok = item->${functionImplementationName}(" . join(", ", @callImplParams) . ");\n");
        my $customNodeAppendChild = << "EOF";
    if (ok)
        return WebKit::kit($returnParamName);
EOF
        push(@cBody, $customNodeAppendChild);
    
        if($raisesException) {
            my $exceptionHandling = << "EOF";

    WebCore::ExceptionCodeDescription ecdesc(ec);
    g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name);
EOF
            push(@cBody, $exceptionHandling);
        }
        push(@cBody, "    return 0;\n");
        push(@cBody, "}\n\n");
        return;
    } elsif ($functionSigType eq "DOMString") {
        my $getterContentHead;
        if ($prefix) {
            my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
            } else {
                $functionName = "item->${functionName}";
            }
            $getterContentHead = "${assign}convertToUTF8String(${functionName}(" . join(", ", @arguments) . "));\n";
        } else {
            my @arguments = @callImplParams;
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $getterContentHead = "${assign}convertToUTF8String(WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . "));\n";
            } else {
                $getterContentHead = "${assign}convertToUTF8String(item->${functionImplementationName}(" . join(", ", @arguments) . "));\n";
            }
        }
        push(@cBody, "    ${getterContentHead}");
    } else {
        my $contentHead;
        if ($prefix eq "get_") {
            my ($functionName, @arguments) = $codeGenerator->GetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
            } else {
                $functionName = "item->${functionName}";
            }
            $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
        } elsif ($prefix eq "set_") {
            my ($functionName, @arguments) = $codeGenerator->SetterExpression(\%implIncludes, $interfaceName, $function);
            push(@arguments, @callImplParams);
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $functionName = "WebCore::${implementedBy}::${functionName}";
                $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
            } else {
                $functionName = "item->${functionName}";
                $contentHead = "${assign}${assignPre}${functionName}(" . join(", ", @arguments) . "${assignPost});\n";
            }
        } else {
            my @arguments = @callImplParams;
            if ($function->signature->extendedAttributes->{"ImplementedBy"}) {
                my $implementedBy = $function->signature->extendedAttributes->{"ImplementedBy"};
                $implIncludes{"${implementedBy}.h"} = 1;
                unshift(@arguments, "item");
                $contentHead = "${assign}${assignPre}WebCore::${implementedBy}::${functionImplementationName}(" . join(", ", @arguments) . "${assignPost});\n";
            } else {
                $contentHead = "${assign}${assignPre}item->${functionImplementationName}(" . join(", ", @arguments) . "${assignPost});\n";
            }
        }
        push(@cBody, "    ${contentHead}");
        
        if($raisesException) {
            my $exceptionHandling = << "EOF";
    if (ec) {
        WebCore::ExceptionCodeDescription ecdesc(ec);
        g_set_error_literal(error, g_quark_from_string("WEBKIT_DOM"), ecdesc.code, ecdesc.name);
    }
EOF
            push(@cBody, $exceptionHandling);
        }
    }

    if ($returnType ne "void" && !$functionHasCustomReturn) {
        if ($functionSigType ne "any") {
            if ($returnValueIsGDOMType) {
                push(@cBody, "    return WebKit::kit(gobjectResult.get());\n");
            } else {
                push(@cBody, "    return result;\n");
            }
        } else {
            push(@cBody, "    return 0; // TODO: return canvas object\n");
        }
    }

    if ($conditionalString) {
        push(@cBody, "#else\n");

        push(@cBody, "    UNUSED_PARAM(self);\n");
        foreach my $param (@{$function->parameters}) {
            push(@cBody, "    UNUSED_PARAM(" . $param->name . ");\n");
        }
        push(@cBody, "    UNUSED_PARAM(error);\n") if $raisesException;

        push(@cBody, @conditionalWarn) if scalar(@conditionalWarn);
        if ($returnType ne "void") {
            if ($codeGenerator->IsNonPointerType($functionSigType)) {
                push(@cBody, "    return static_cast<${returnType}>(0);\n");
            } else {
                push(@cBody, "    return 0;\n");
            }
        }
        push(@cBody, "#endif /* ${conditionalString} */\n");
    }

    if ($parentConditionalString) {
        push(@cBody, "#else\n");

        push(@cBody, "    UNUSED_PARAM(self);\n");
        foreach my $param (@{$function->parameters}) {
            push(@cBody, "    UNUSED_PARAM(" . $param->name . ");\n");
        }
        push(@cBody, "    UNUSED_PARAM(error);\n") if $raisesException;

        push(@cBody, @parentConditionalWarn) if scalar(@parentConditionalWarn);
        if ($returnType ne "void") {
            if ($codeGenerator->IsNonPointerType($functionSigType)) {
                push(@cBody, "    return static_cast<${returnType}>(0);\n");
            } else {
                push(@cBody, "    return 0;\n");
            }
        }
        push(@cBody, "#endif /* ${parentConditionalString} */\n");
    }

    push(@cBody, "}\n\n");
}

sub ClassHasFunction {
    my ($class, $name) = @_;

    foreach my $function (@{$class->functions}) {
        if ($function->signature->name eq $name) {
            return 1;
        }
    }

    return 0;
}

sub GenerateFunctions {
    my ($object, $interfaceName, $interface) = @_;

    foreach my $function (@{$interface->functions}) {
        $object->GenerateFunction($interfaceName, $function, "", $interface);
    }

    TOP:
    foreach my $attribute (@{$interface->attributes}) {
        if (SkipAttribute($attribute)) {
            next TOP;
        }

        my $attrNameUpper = $codeGenerator->WK_ucfirst($attribute->signature->name);
        my $getname = "get${attrNameUpper}";
        my $setname = "set${attrNameUpper}";
        if (ClassHasFunction($interface, $getname) || ClassHasFunction($interface, $setname)) {
            # Very occasionally an IDL file defines getter/setter functions for one of its
            # attributes; in this case we don't need to autogenerate the getter/setter.
            next TOP;
        }
        
        # Generate an attribute getter.  For an attribute "foo", this is a function named
        # "get_foo" which calls a DOM class method named foo().
        my $function = new domFunction();
        $function->signature($attribute->signature);
        $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}});
        if ($attribute->signature->extendedAttributes->{"GetterRaisesException"}) {
            $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING";
        }
        $object->GenerateFunction($interfaceName, $function, "get_", $interface);

        # FIXME: We are not generating setters for 'Replaceable'
        # attributes now, but we should somehow.
        my $custom = $attribute->signature->extendedAttributes->{"CustomSetter"};
        if ($attribute->isReadOnly || $attribute->signature->extendedAttributes->{"Replaceable"} || $custom) {
            next TOP;
        }
        
        # Generate an attribute setter.  For an attribute, "foo", this is a function named
        # "set_foo" which calls a DOM class method named setFoo().
        $function = new domFunction();
        
        $function->signature(new domSignature());
        $function->signature->name($attribute->signature->name);
        $function->signature->type($attribute->signature->type);
        $function->signature->extendedAttributes({%{$attribute->signature->extendedAttributes}});
        
        my $param = new domSignature();
        $param->name("value");
        $param->type($attribute->signature->type);
        my %attributes = ();
        $param->extendedAttributes(\%attributes);
        my $arrayRef = $function->parameters;
        push(@$arrayRef, $param);
        
        if ($attribute->signature->extendedAttributes->{"SetterRaisesException"}) {
            $function->signature->extendedAttributes->{"RaisesException"} = "VALUE_IS_MISSING";
        } else {
            delete $function->signature->extendedAttributes->{"RaisesException"};
        }
        
        $object->GenerateFunction($interfaceName, $function, "set_", $interface);
    }
}

sub GenerateCFile {
    my ($object, $interfaceName, $parentClassName, $parentGObjType, $interface) = @_;

    if ($interface->extendedAttributes->{"EventTarget"}) {
        $object->GenerateEventTargetIface($interface);
    }

    my $implContent = "";

    my $decamelize = decamelize($interfaceName);
    my $clsCaps = uc($decamelize);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";
    my $parentImplClassName = GetParentImplClassName($interface);
    my $baseClassName = GetBaseClass($parentImplClassName, $interface);

    # Add a private struct only for direct subclasses of Object so that we can use RefPtr
    # for the WebCore wrapped object and make sure we only increment the reference counter once.
    if ($parentImplClassName eq "Object") {
        my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
        push(@cStructPriv, "#define WEBKIT_DOM_${clsCaps}_GET_PRIVATE(obj) G_TYPE_INSTANCE_GET_PRIVATE(obj, WEBKIT_DOM_TYPE_${clsCaps}, ${className}Private)\n\n");
        push(@cStructPriv, "typedef struct _${className}Private {\n");
        push(@cStructPriv, "#if ${conditionalString}\n") if $conditionalString;
        push(@cStructPriv, "    RefPtr<WebCore::${interfaceName}> coreObject;\n");
        push(@cStructPriv, "#endif // ${conditionalString}\n") if $conditionalString;
        push(@cStructPriv, "} ${className}Private;\n\n");
    }

    $implContent = << "EOF";
${defineTypeMacro}(${className}, ${lowerCaseIfaceName}, ${parentGObjType}${defineTypeInterfaceImplementation}

EOF
    push(@cBodyProperties, $implContent);

    if ($parentImplClassName eq "Object") {
        push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n");
        push(@cBodyPriv, "{\n");
        push(@cBodyPriv, "    if (!obj)\n");
        push(@cBodyPriv, "        return 0;\n\n");
        push(@cBodyPriv, "    if (gpointer ret = DOMObjectCache::get(obj))\n");
        push(@cBodyPriv, "        return WEBKIT_DOM_${clsCaps}(ret);\n\n");
        if (IsPolymorphic($interfaceName)) {
            push(@cBodyPriv, "    return wrap(obj);\n");
        } else {
            push(@cBodyPriv, "    return wrap${interfaceName}(obj);\n");
        }
        push(@cBodyPriv, "}\n\n");
    } else {
        push(@cBodyPriv, "${className}* kit(WebCore::$interfaceName* obj)\n");
        push(@cBodyPriv, "{\n");
        if (!IsPolymorphic($baseClassName)) {
            push(@cBodyPriv, "    if (!obj)\n");
            push(@cBodyPriv, "        return 0;\n\n");
            push(@cBodyPriv, "    if (gpointer ret = DOMObjectCache::get(obj))\n");
            push(@cBodyPriv, "        return WEBKIT_DOM_${clsCaps}(ret);\n\n");
            push(@cBodyPriv, "    return wrap${interfaceName}(obj);\n");
        } else {
            push(@cBodyPriv, "    return WEBKIT_DOM_${clsCaps}(kit(static_cast<WebCore::$baseClassName*>(obj)));\n");
        }
        push(@cBodyPriv, "}\n\n");
    }

    $implContent = << "EOF";
WebCore::${interfaceName}* core(${className}* request)
{
    return request ? static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(request)->coreObject) : 0;
}

${className}* wrap${interfaceName}(WebCore::${interfaceName}* coreObject)
{
    ASSERT(coreObject);
    return WEBKIT_DOM_${clsCaps}(g_object_new(WEBKIT_DOM_TYPE_${clsCaps}, "core-object", coreObject, nullptr));
}

EOF
    push(@cBodyPriv, $implContent);

    $object->GenerateProperties($interfaceName, $interface);
    $object->GenerateFunctions($interfaceName, $interface);
}

sub GenerateEndHeader {
    my ($object) = @_;

    my $isStableClass = scalar(@stableSymbols);
    if (!$isStableClass) {
        push(@hPrefixGuardEnd, "#endif /* WEBKIT_DOM_USE_UNSTABLE_API */\n");
    }

    #Header guard
    my $guard = $className . "_h";

    push(@hBody, "G_END_DECLS\n\n");
    push(@hPrefixGuardEnd, "#endif /* $guard */\n");
}

sub IsPolymorphic {
    my $type = shift;

    return scalar(grep {$_ eq $type} qw(Blob Event HTMLCollection Node StyleSheet TextTrackCue));
}

sub GenerateEventTargetIface {
    my $object = shift;
    my $interface = shift;

    my $interfaceName = $interface->name;
    my $decamelize = decamelize($interfaceName);
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);
    my @conditionalWarn = GenerateConditionalWarning($interface);

    $implIncludes{"GObjectEventListener.h"} = 1;
    $implIncludes{"WebKitDOMEventTarget.h"} = 1;
    $implIncludes{"WebKitDOMEventPrivate.h"} = 1;

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_dispatch_event(WebKitDOMEventTarget* target, WebKitDOMEvent* event, GError** error)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::Event* coreEvent = WebKit::core(event);\n");
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n\n");
    push(@cBodyProperties, "    WebCore::ExceptionCode ec = 0;\n");
    push(@cBodyProperties, "    gboolean result = coreTarget->dispatchEvent(coreEvent, ec);\n");
    push(@cBodyProperties, "    if (ec) {\n        WebCore::ExceptionCodeDescription description(ec);\n");
    push(@cBodyProperties, "        g_set_error_literal(error, g_quark_from_string(\"WEBKIT_DOM\"), description.code, description.name);\n    }\n");
    push(@cBodyProperties, "    return result;\n");
    if ($conditionalString) {
        push(@cBodyProperties, "#else\n");
        push(@cBodyProperties, "    UNUSED_PARAM(target);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(event);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(error);\n");
        push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
        push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n");
    }
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_add_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n");
    push(@cBodyProperties, "    return WebCore::GObjectEventListener::addEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n");
    if ($conditionalString) {
        push(@cBodyProperties, "#else\n");
        push(@cBodyProperties, "    UNUSED_PARAM(target);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(eventName);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(handler);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(useCapture);\n");
        push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
        push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n");
    }
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static gboolean webkit_dom_${decamelize}_remove_event_listener(WebKitDOMEventTarget* target, const char* eventName, GClosure* handler, gboolean useCapture)\n{\n");
    push(@cBodyProperties, "#if ${conditionalString}\n") if $conditionalString;
    push(@cBodyProperties, "    WebCore::${interfaceName}* coreTarget = static_cast<WebCore::${interfaceName}*>(WEBKIT_DOM_OBJECT(target)->coreObject);\n");
    push(@cBodyProperties, "    return WebCore::GObjectEventListener::removeEventListener(G_OBJECT(target), coreTarget, eventName, handler, useCapture);\n");
    if ($conditionalString) {
        push(@cBodyProperties, "#else\n");
        push(@cBodyProperties, "    UNUSED_PARAM(target);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(eventName);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(handler);\n");
        push(@cBodyProperties, "    UNUSED_PARAM(useCapture);\n");
        push(@cBodyProperties, @conditionalWarn) if scalar(@conditionalWarn);
        push(@cBodyProperties, "    return false;\n#endif // ${conditionalString}\n");
    }
    push(@cBodyProperties, "}\n\n");

    push(@cBodyProperties, "static void webkit_dom_event_target_init(WebKitDOMEventTargetIface* iface)\n{\n");
    push(@cBodyProperties, "    iface->dispatch_event = webkit_dom_${decamelize}_dispatch_event;\n");
    push(@cBodyProperties, "    iface->add_event_listener = webkit_dom_${decamelize}_add_event_listener;\n");
    push(@cBodyProperties, "    iface->remove_event_listener = webkit_dom_${decamelize}_remove_event_listener;\n}\n\n");

    $defineTypeMacro = "G_DEFINE_TYPE_WITH_CODE";
    $defineTypeInterfaceImplementation = ", G_IMPLEMENT_INTERFACE(WEBKIT_DOM_TYPE_EVENT_TARGET, webkit_dom_event_target_init))";
}

sub Generate {
    my ($object, $interface) = @_;

    my $parentClassName = GetParentClassName($interface);
    my $parentGObjType = GetParentGObjType($interface);
    my $interfaceName = $interface->name;
    my $parentImplClassName = GetParentImplClassName($interface);
    my $baseClassName = GetBaseClass($parentImplClassName, $interface);

    # Add the default impl header template
    @cPrefix = split("\r", $licenceTemplate);
    push(@cPrefix, "\n");

    $implIncludes{"DOMObjectCache.h"} = 1;
    $implIncludes{"WebKitDOMPrivate.h"} = 1;
    $implIncludes{"gobject/ConvertToUTF8String.h"} = 1;
    $implIncludes{"${className}Private.h"} = 1;
    $implIncludes{"Document.h"} = 1;
    $implIncludes{"JSMainThreadExecState.h"} = 1;
    $implIncludes{"ExceptionCode.h"} = 1;
    $implIncludes{"ExceptionCodeDescription.h"} = 1;
    $implIncludes{"CSSImportRule.h"} = 1;
    if ($parentImplClassName ne "Object" and IsPolymorphic($baseClassName)) {
        $implIncludes{"WebKitDOM${baseClassName}Private.h"} = 1;
    }

    $hdrIncludes{"webkitdom/${parentClassName}.h"} = 1;

    $object->GenerateHeader($interfaceName, $parentClassName, $interface);
    $object->GenerateCFile($interfaceName, $parentClassName, $parentGObjType, $interface);
    $object->GenerateEndHeader();
}

sub WriteData {
    my $object = shift;
    my $interface = shift;
    my $outputDir = shift;
    mkdir $outputDir;

    my $isStableClass = scalar(@stableSymbols);

    # Write a private header.
    my $interfaceName = $interface->name;
    my $filename = "$outputDir/" . $className . "Private.h";
    my $guard = "${className}Private_h";

    # Add the guard if the 'Conditional' extended attribute exists
    my $conditionalString = $codeGenerator->GenerateConditionalString($interface);

    open(PRIVHEADER, ">$filename") or die "Couldn't open file $filename for writing";

    print PRIVHEADER split("\r", $licenceTemplate);
    print PRIVHEADER "\n";

    my $text = << "EOF";
#ifndef $guard
#define $guard

#include "${interfaceName}.h"
#include <webkitdom/${className}.h>
EOF

    print PRIVHEADER $text;
    print PRIVHEADER "#if ${conditionalString}\n" if $conditionalString;
    print PRIVHEADER "\n";
    $text = << "EOF";
namespace WebKit {
${className}* wrap${interfaceName}(WebCore::${interfaceName}*);
${className}* kit(WebCore::${interfaceName}*);
WebCore::${interfaceName}* core(${className}*);
EOF

    print PRIVHEADER $text;

    $text = << "EOF";
} // namespace WebKit

EOF

    print PRIVHEADER $text;
    print PRIVHEADER "#endif /* ${conditionalString} */\n\n" if $conditionalString;
    print PRIVHEADER "#endif /* ${guard} */\n";

    close(PRIVHEADER);

    my $basename = FileNamePrefix . $interfaceName;
    $basename =~ s/_//g;

    # Write public header.
    my $fullHeaderFilename = "$outputDir/" . $basename . ".h";
    my $installedHeaderFilename = "${basename}.h";
    open(HEADER, ">$fullHeaderFilename") or die "Couldn't open file $fullHeaderFilename";

    print HEADER @hPrefix;
    print HEADER @hPrefixGuard;
    print HEADER "#include <glib-object.h>\n";
    print HEADER map { "#include <$_>\n" } sort keys(%hdrIncludes);
    if ($isStableClass) {
        print HEADER "#include <webkitdom/webkitdomdefines.h>\n\n";
    } else {
        print HEADER "#include <webkitdom/webkitdomdefines-unstable.h>\n\n";
    }
    print HEADER @hBodyPre;
    print HEADER @hBody;
    print HEADER @hPrefixGuardEnd;

    close(HEADER);

    # Write the unstable header if needed.
    if ($isStableClass and scalar(@hBodyUnstable)) {
        my $fullUnstableHeaderFilename = "$outputDir/" . $className . "Unstable.h";
        open(UNSTABLE, ">$fullUnstableHeaderFilename") or die "Couldn't open file $fullUnstableHeaderFilename";

        print UNSTABLE split("\r", $licenceTemplate);
        print UNSTABLE "\n";

        $guard = "${className}Unstable_h";
        $text = << "EOF";
#ifndef $guard
#define $guard

#ifdef WEBKIT_DOM_USE_UNSTABLE_API

#include <webkitdom/webkitdomdefines-unstable.h>
EOF

        print UNSTABLE $text;
        print UNSTABLE "\n";
        print UNSTABLE "#if ${conditionalString}\n\n" if $conditionalString;
        print UNSTABLE "G_BEGIN_DECLS\n";
        print UNSTABLE "\n";
        print UNSTABLE @hBodyUnstable;
        print UNSTABLE "\n";
        print UNSTABLE "G_END_DECLS\n";
        print UNSTABLE "\n";
        print UNSTABLE "#endif /* ${conditionalString} */\n\n" if $conditionalString;
        print UNSTABLE "#endif /* WEBKIT_DOM_USE_UNSTABLE_API */\n";
        print UNSTABLE "#endif /* ${guard} */\n";

        close(UNSTABLE);
    }

    # Write the implementation sources
    my $implFileName = "$outputDir/" . $basename . ".cpp";
    open(IMPL, ">$implFileName") or die "Couldn't open file $implFileName";

    print IMPL @cPrefix;
    print IMPL "#include \"config.h\"\n";
    print IMPL "#include \"$installedHeaderFilename\"\n\n";

    # Remove the implementation header from the list of included files.
    %includesCopy = %implIncludes;
    print IMPL map { "#include \"$_\"\n" } sort keys(%includesCopy);
    if ($isStableClass and scalar(@hBodyUnstable)) {
        print IMPL "#include \"${className}Unstable.h\"\n";
    }

    print IMPL "#include <wtf/GetPtr.h>\n";
    print IMPL "#include <wtf/RefPtr.h>\n\n";
    print IMPL @cStructPriv;
    print IMPL "#if ${conditionalString}\n\n" if $conditionalString;

    print IMPL "namespace WebKit {\n\n";
    print IMPL @cBodyPriv;
    print IMPL "} // namespace WebKit\n\n";
    print IMPL "#endif // ${conditionalString}\n\n" if $conditionalString;

    print IMPL @cBodyProperties;
    print IMPL @cBody;

    close(IMPL);

    # Write a symbols file.
    if ($isStableClass) {
        my $symbolsFileName = "$outputDir/" . $basename . ".symbols";
        open(SYM, ">$symbolsFileName") or die "Couldn't open file $symbolsFileName";
        print SYM @symbols;
        close(SYM);
    }

    %implIncludes = ();
    %hdrIncludes = ();
    @hPrefix = ();
    @hBody = ();
    @hBodyUnstable = ();

    @cPrefix = ();
    @cBody = ();
    @cBodyPriv = ();
    @cBodyProperties = ();
    @cStructPriv = ();

    @symbols = ();
    @stableSymbols = ();
}

sub IsInterfaceSymbol {
    my ($line, $lowerCaseIfaceName) = @_;

    # Function.
    return 1 if $line =~ /^[a-zA-Z0-9\*]+\s${lowerCaseIfaceName}_.+$/;

    # Constant.
    my $prefix = uc($lowerCaseIfaceName);
    return 1 if $line =~ /^${prefix}_[A-Z_]+$/;
    return 0;
}

sub ReadStableSymbols {
    my $interfaceName = shift;

    @stableSymbols = ();

    my $bindingsDir = dirname($FindBin::Bin);
    my $fileName = "$bindingsDir/gobject/webkitdom.symbols";
    open FILE, "<", $fileName or die "Could not open $fileName";
    my @lines = <FILE>;
    close FILE;

    my $decamelize = decamelize($interfaceName);
    my $lowerCaseIfaceName = "webkit_dom_$decamelize";

    foreach $line (@lines) {
        $line =~ s/\n$//;

        if ($line eq "GType ${lowerCaseIfaceName}_get_type(void)") {
            push(@stableSymbols, $line);
            next;
        }

        if (scalar(@stableSymbols) and IsInterfaceSymbol($line, $lowerCaseIfaceName) and $line !~ /^GType/) {
            push(@stableSymbols, $line);
            next;
        }

        if (scalar(@stableSymbols) and $line !~ /^GType/) {
            warn "Symbol %line found, but a get_type was expected";
        }

        last if scalar(@stableSymbols);
    }
}

sub GenerateInterface {
    my ($object, $interface, $defines) = @_;

    # Set up some global variables
    $className = GetClassName($interface->name);

    ReadStableSymbols($interface->name);

    $object->Generate($interface);
}

1;
